unit CPGDIPlotCanvas;

interface

uses Types, Classes, Controls, Graphics,
  CPPlotCanvas, CPBase;

type
  { This class implements GDI painting methods defined by TCPPlotCanvas.
    Canvas units are interpreted as pixels. }
  TCPGDIPlotCanvas = class(TCPPlotCanvas)
  private
    { Half line thickness in pixel }
    FPenHalfPixelWidth: Integer;
    { Rectangle used in HorizLine() for optimization purposes }
    FHorizLineRect: TRect;
    { Rectangle used in VertLine() for optimization purposes }
    FVertLineRect: TRect;
    class function ToRect(Bounds: TCPBounds): TRect;
  protected
    procedure Resize; override;
    procedure SetBrushColor(const Value: TColor); override;
    procedure SetPenColor(const Value: TColor); override;
    procedure SetPenWidth(const Value: TCPFloat); override;
    procedure FontChanged; override;
  public
    { Create the canvas on the referenced parent control.
      @param Control Plotter control) }
    constructor Create(Control: TCustomControl;
      UnitsPerInch: TCPFloat); override;
    function SmoothDistance(Distance: TCPFloat): TCPFloat; override;
    procedure MoveTo(X, Y: TCPFloat); override;
    procedure LineTo(X, Y: TCPFloat); override;
    procedure HorizLine(Y: TCPFloat); override;
    procedure VertLine(X: TCPFloat); override;
    procedure Ellipse(const Bounds: TCPBounds); override;
    procedure Rectangle(const Bounds: TCPBounds); override;
    procedure FrameRect(const Bounds: TCPBounds); override;
    procedure FillRect(const Bounds: TCPBounds); override;
    procedure TextOut(X, Y: TCPFloat; const Text: string); override;
end;

implementation

uses Math, Windows, CPPlotter;


{ TCPGDIPlotCanvas }

class function TCPGDIPlotCanvas.ToRect(Bounds: TCPBounds): TRect;
begin
  Result.Left := Round(Bounds.Left);
  Result.Top := Round(Bounds.Top);
  Result.Right := Result.Left + Round(Bounds.Width);
  Result.Bottom := Result.Top + Round(Bounds.Height);
end;

constructor TCPGDIPlotCanvas.Create(Control: TCustomControl;
  UnitsPerInch: TCPFloat);
begin
  inherited;
  { Unset Opaque flag, because the control already draws the background color: }
  FControl.ControlStyle := FControl.ControlStyle - [csOpaque];
  { Enable doublebuffering for non-flickering animation: }
  FControl.DoubleBuffered := true;
end;

procedure TCPGDIPlotCanvas.Ellipse(const Bounds: TCPBounds);
begin
  FCanvas.Ellipse(ToRect(Bounds));
end;

procedure TCPGDIPlotCanvas.FillRect(const Bounds: TCPBounds);
begin
  FCanvas.FillRect(ToRect(Bounds));
end;

procedure TCPGDIPlotCanvas.FrameRect(const Bounds: TCPBounds);
begin
  FCanvas.FrameRect(ToRect(Bounds));
end;

procedure TCPGDIPlotCanvas.HorizLine(Y: TCPFloat);
var
  PenPos: TPoint;
begin
  {FCanvas.Brush.Color := FPenColor;
  FHorizLineRect.Top := Round(Y) - FPenHalfPixelWidth;
  FHorizLineRect.Bottom := FHorizLineRect.Top + FPenDiscreteUnitWidth;
  FCanvas.FillRect(FHorizLineRect);
  FCanvas.Brush.Color := FBrushColor;}
  PenPos := FCanvas.PenPos;
  FCanvas.MoveTo(0, Round(Y));
  FCanvas.LineTo(FControl.Width, Round(Y));
  FCanvas.PenPos := PenPos;
end;

procedure TCPGDIPlotCanvas.LineTo(X, Y: TCPFloat);
begin
  FCanvas.LineTo(CPClipRound(X), CPClipRound(Y));
  inherited;
end;

procedure TCPGDIPlotCanvas.MoveTo(X, Y: TCPFloat);
begin
  inherited;
  FCanvas.MoveTo(CPClipRound(X), CPClipRound(Y));
end;

procedure TCPGDIPlotCanvas.Rectangle(const Bounds: TCPBounds);
begin
  FCanvas.Rectangle(ToRect(Bounds));

end;

procedure TCPGDIPlotCanvas.SetBrushColor(const Value: TColor);
begin
  inherited;
  FCanvas.Brush.Color := Value;
end;

procedure TCPGDIPlotCanvas.SetPenColor(const Value: TColor);
begin
  inherited;
  FCanvas.Pen.Color := Value;
end;

procedure TCPGDIPlotCanvas.TextOut(X, Y: TCPFloat; const Text: string);
begin
  SetBkMode(FCanvas.Handle, TRANSPARENT);
  FCanvas.Font.Color := FPenColor;
  Windows.TextOut(FCanvas.Handle, Round(X), Round(Y),
    PChar(Text), Length(Text));
end;

procedure TCPGDIPlotCanvas.VertLine(X: TCPFloat);
var
  PenPos: TPoint;
begin
  {FCanvas.Brush.Color := FPenColor;
  FVertLineRect.Left := Round(X) - FPenHalfPixelWidth;
  FVertLineRect.Right := FVertLineRect.Left + FPenDiscreteUnitWidth;
  FCanvas.FillRect(FVertLineRect);
  FCanvas.Brush.Color := FBrushColor;}
  PenPos := FCanvas.PenPos;
  FCanvas.MoveTo(Round(X), 0);
  FCanvas.LineTo(Round(X), FControl.Height);
  FCanvas.PenPos := PenPos;
end;

procedure TCPGDIPlotCanvas.SetPenWidth(const Value: TCPFloat);
begin
  inherited;
  FPenHalfPixelWidth := FPenDiscreteUnitWidth shr 1;
  FCanvas.Pen.Width := FPenDiscreteUnitWidth;
end;

procedure TCPGDIPlotCanvas.Resize;
begin
  FVertLineRect.Top := Round(FBounds.Top + FBounds.Height);
  FVertLineRect.Bottom := Round(FBounds.Top);
  FHorizLineRect.Left := Round(FBounds.Left);
  FHorizLineRect.Right := Round(FBounds.Left + FBounds.Width);
end;

function TCPGDIPlotCanvas.SmoothDistance(Distance: TCPFloat): TCPFloat;
begin
  Result := Round(Distance);
end;

procedure TCPGDIPlotCanvas.FontChanged;
begin
  inherited;
  FCanvas.Font.Height := -Round(FFontHeight);
end;

end.

